﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace JobDemo01.Job
{
    class ResultWriter : IDisposable
    {
        readonly JobResultContext ctx;
        readonly ExecutionPlan plan;

        readonly string alignBasePath;
        readonly string addrBasePath;
        readonly string rawBasePath;

        public ResultWriter(JobResultContext ctx, ExecutionPlan plan)
        {
            this.ctx = ctx;
            this.plan = plan;

            var options = plan.Options;
            alignBasePath = Path.Join(options.OutputDirectory, "align");
            addrBasePath = Path.Join(options.OutputDirectory, "addr");
            rawBasePath = Path.Join(options.OutputDirectory, "raw");
            Directory.CreateDirectory(alignBasePath);
            Directory.CreateDirectory(addrBasePath);
            Directory.CreateDirectory(rawBasePath);
        }

        public Task Delete(int scanIndex)
        {
            return ctx.Images
                 .Where(img => img.JobResultId == plan.Id && img.ScanIndex == scanIndex)
                 .ExecuteDeleteAsync();
        }

        public async Task Write(PatternResult result)
        {
            await using var transaction = await ctx.Database.BeginTransactionAsync();
            var image = result.Image;
            switch (result.Image.Kind)
            {
                case PatternKind.High:
                    Marker.Draw(image.ImageData, image.MatchDx, image.MatchDy);
                    SavePatternImage(alignBasePath, "High", image, ImageKind.High);
                    break;
                case PatternKind.Align:
                    Save(rawBasePath, "Align", image);
                    Marker.Draw(image.ImageData, image.MatchDx, image.MatchDy);
                    SavePatternImage(alignBasePath, "Align", image, ImageKind.Align);
                    break;
                case PatternKind.Mp:
                    if (image.Addressing != null)
                    {
                        Save(rawBasePath, "AP", image.Addressing);
                        Marker.Draw(image.Addressing.ImageData, image.Addressing.MatchDx, image.Addressing.MatchDy);
                        SavePatternImage(addrBasePath, "AP", image.Addressing, ImageKind.Addr);
                    }

                    if (image.ImageData != null)
                    {
                        SavePatternImage(rawBasePath, "P", image, ImageKind.Raw);
                    }

                    if (result.Values.Length == 0)
                    {
                        var status = image.ThresholdReached ? MeasurementStatus.Finished : MeasurementStatus.Failed;
                        var id = plan.Measurements[(result.Index, 0)];
                        await ctx.Measurements
                               .Where(m => m.Id == id)
                               .ExecuteUpdateAsync(setters => setters.SetProperty(m => m.Status, status))
                           .ConfigureAwait(false);
                    }
                    else
                    {
                        for (var i = 0; i < result.Values.Length; i++)
                        {
                            var (img, v) = result.Values[i];
                            var id = plan.Measurements[(result.Index, i)];
                            await ctx.Measurements
                                .Where(m => m.Id == id)
                                .ExecuteUpdateAsync(setters => setters
                                    .SetProperty(m => m.Value, v)
                                    .SetProperty(m => m.Status, MeasurementStatus.Finished))
                            .ConfigureAwait(false);
                            var c = image.Coordinate;
                            var pattern = image.Pattern;
                            var name = $"P{result.Index}-{pattern.CDPoints[i].Name}-{c.X}-{c.Y}.png";
                            var path = Path.Join(plan.Options.OutputDirectory, name);
                            Save(path, img);
                            ctx.Images.Add(new PatternImage
                            {
                                Kind = ImageKind.MP,
                                ScanIndex = result.Index,
                                Index = i,
                                Filename = path,
                                JobResultId = plan.Id,
                            });
                        }
                    }
                    break;
            }
            await transaction.CommitAsync();
            await ctx.SaveChangesAsync().ConfigureAwait(false);
        }

        public async Task WriteEndDate(DateTimeOffset time)
        {
            await ctx.JobResults
                .Where(job => job.Id == plan.Id)
                .ExecuteUpdateAsync(setters => setters
                    .SetProperty(r => r.End, time.ToUnixTimeMilliseconds())
                    .SetProperty(r => r.IsConfirmed, !plan.Environment.RequireConfirmation))
                .ConfigureAwait(false);
        }

        public void Dispose()
        {
            ctx.Dispose();
        }

        void SavePatternImage(string basePath, string prefix, ImagingResult img, ImageKind kind)
        {
            var path = GetPath(basePath, prefix, img);
            Save(path, img.ImageData);
            ctx.Images.Add(new PatternImage
            {
                Kind = kind,
                ScanIndex = img.Index,
                Filename = path,
                JobResultId = plan.Id,
            });
        }

        static string GetPath(string basePath, string prefix, ImagingResult img)
        {
            var c = img.Coordinate;
            return Path.Join(basePath, $"{prefix}{img.Index}-{c.X}-{c.Y}.png");
        }

        static void Save(string basePath, string prefix, ImagingResult img)
        {
            var path = GetPath(basePath, prefix, img);
            Save(path, img.ImageData);
        }

        static void Save(string path, ImageData img)
        {
            using var stream = new FileStream(path, FileMode.Create);
            var encoder = new PngBitmapEncoder();

            var (w, h, data, _) = img;
            BitmapSource image = BitmapSource.Create(
                w,
                h,
                96,
                96,
                PixelFormats.Gray8,
                null,
                data,
                w);
            encoder.Frames.Add(BitmapFrame.Create(image));
            encoder.Save(stream);
        }
    }
}
